/*
 	Pulse.cpp

 	Copyright (c) 2008 Bojan Rajkovic

 	Permission is hereby granted, free of charge, to any person obtaining a copy
 	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
 	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 	copies of	 the Software, and to permit persons to whom the Software is
 	furnished to do so, subject to the following conditions:
	
 	The above copyright notice and this permission notice shall be included in
 	all copies or substantial portions of the Software.

 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 	THE SOFTWARE.
*/

#include "../include/Pulse.h"

// pulse_to_web returns the HTTP status code--ideally you want to get back a 200 OK.
// if we failed somewhere along the way, we get back a -1
unsigned int Pulse::PulseToWeb(PulseInfo Pulse) {
		
	// set up our variables
	int statusCode;

	// Pull the important stuff out of the configuration file
	Config config;
	config.readFile(strdup(DEFAULT_CONFIG_FILE));
	std::string pulseServer(static_cast<const char*>(config.lookup("application.pulse.server")));
	unsigned int pulseServerPort = config.lookup("application.pulse.port");
	
	IPv4ClientSocket Socket;
	
	try {
		Socket.Open(pulseServer, pulseServerPort);
	} catch (SocketException ex) {
		Utility::LogMessage(ex.what(), M_ERROR);
		return -1;
	}

	/* now let's build what we'll be asking the server for */
	std::string requestString = Utility::FormatString(strdup(PULSE_REQUEST), Pulse.UserName().c_str(), 
		Utility::SHA1Hash(Pulse.Password()).c_str(), Pulse.Keys(), pulseServer.c_str(), strdup(KEYCOUNTER_VERSION));
	Utility::LogMessage(requestString, M_INFO);
	
	// Send it off to the socket, we'll get an exception if it fails
	try {
		Socket.Write(requestString);
	} catch (SocketException ex) {
		Utility::LogMessage(ex.what(), M_ERROR);
	}

	// we're expecting HTTP/1.x ### where ### is a status code.
	// hopefully, 200 is what we get there, but we may not.
	// Let's read the line out
	std::string serverResponse;
	try {
		serverResponse = Socket.ReadLine();
	} catch (SocketException ex) {
		Utility::LogMessage(ex.what(), M_ERROR);
		return -1;
	}
	RE re("(\\d{3})");
    re.PartialMatch(serverResponse, &statusCode);

	// now, let's parse the status code
	switch (statusCode) {
		case HTTP_STATUS_CODE_OK: //200
			return HTTP_STATUS_CODE_OK;
		// we really should never be seeing these codes, unless the web service is moving to a different
		// hostname, because we really don't have the functionality to change our entire URL because the
		// server operator wants to use a different uri. let him write a redirect server-side for that.
		// we'll log them for posterity, and move on, because they may be useful someday.
		// Also, we're handling these here because the server actually needs to give us some information with
		// these. Other status codes don't have any accompanying information, so we'll let handle_http_error()
		// deal with them.
		case HTTP_STATUS_CODE_MOVED_PERMANENTLY: //301
		case HTTP_STATUS_CODE_FOUND: // 302
		case HTTP_STATUS_CODE_SEE_OTHER: //303
		case HTTP_STATUS_CODE_MOVED_TEMPORARILY:  {//307
			// okay, so we know the status code, and we know we should be seeing a location header
			// let's read a line at a time, and use regular expressions to look for the Location: header
			// editor's note: i fucking love regexp's and pcrecpp.
			char *header_name, *uri;
			std::string responseLine = Socket.ReadLine();
			while (responseLine.size() > 0) {
				// let's see what header we've got. we're gonna use a regex for this again
				// the regex :.*$ will get us the header name.
				RE re("(:.*$)");
				re.PartialMatch(responseLine, header_name);
				if (strcmp(header_name,strdup("Location")) == 0) { // okay, so we've got the location header
					// the regex ^\w+: will get us the location uri
					RE re("(^.+:?)");
					re.PartialMatch(responseLine, uri);
				}
				responseLine = Socket.ReadLine();
			}
			// if the uri is null, we must've not found a location header
			// in that case, the server is badly misconfigured, since 30x should never be sent
			// without a location header
			if (uri == NULL) {
				uri = strdup(_("Location header not found, URI not set."));
			}
			// format the message, log it
			std::string message = Utility::FormatString(_("Got a 30x error code from the server: it says %i, new URI %s"), statusCode, uri);
			Utility::LogMessage(message, M_ERROR);
			// free up our allocated buffers, close the socket and the file descriptor
			// and return the status code
			free(uri);
			free(header_name);
			return statusCode;
			break;
		}
		default:
			// let handle_http_error handle other status codes
			// since there's no associated message with any others
			Pulse::HandleHTTPError(statusCode);
			// close up sockets and file descriptors
			return statusCode;
	}
	return statusCode;
}

// zero the pulse file
void Pulse::ZeroPulseFile() {
	// we want to just remove the old file
	remove("/var/lib/linpulse/pulse");
	// we're ignoring errors because the file might not even exist
	// catching the error would be silly
}

// handle http errors other than 30x
void Pulse::HandleHTTPError(int ErrorCode) {
		switch (ErrorCode) {
		// These we're actually handling here.
		case HTTP_STATUS_CODE_BAD_REQUEST: //400
			Utility::LogMessage(_("Tried to pulse, server told us 400 Bad Request, you should probably file a bug."), M_ERROR);
			break;
		case HTTP_STATUS_CODE_FORBIDDEN: //402
			Utility::LogMessage(_("Tried to pulse, server told us 402 Forbidden, you should check with your pulse server owner."), M_ERROR);
			break;
		case HTTP_STATUS_CODE_NOT_FOUND: //404 /* in essence, this and 410 are the same */
			Utility::LogMessage(_("Tried to pulse, server told us 404 Not Found, you should check with your pulse server owner."), M_ERROR);
			break;
		case HTTP_STATUS_CODE_GONE: //410
			Utility::LogMessage(_("Tried to pulse, server told us 410 Gone (similar to 404), you should check with your pulse server owner."), M_ERROR);
			break;
		default:
			Utility::LogMessage(Utility::FormatString(_("Server returned error code %i, please check its meaning and check on it with your pulse server owner."), ErrorCode), M_ERROR);
			break;
	}
}

// save the keycount to the pulse file
void Pulse::SavePulseFile(unsigned long Keys) {
	// Remove the old file first
	if (remove("/var/lib/linpulse/pulse") < 0) {
		throw LinPulse::KeycounterException(strerror(errno));
	}
	ofstream pulseFile("/var/lib/linpulse/pulse");
	if (!pulseFile.good()) {
		throw LinPulse::KeycounterException("Could not write out to pulse file, make sure the daemon has write permissions to it.");
	}
	pulseFile << Keys;
	pulseFile.close();
}

// pulse_to_db returns the sqlite3 return value, which can be checked too.
// can also return -1 if there were errors
unsigned int Pulse::PulseToDB(PulseInfo Pulse) {
	Config config;
	config.readFile(strdup(DEFAULT_CONFIG_FILE));
	std::string databasePath = config.lookup("application.pulse.db");
	int retval;
	try {
		// create an instance of the wrapper class
		SQLiteWrapper database(databasePath);
		// check if the table exists
		retval = database.TableExists("pulse_logs");

		switch (retval) {
			// er, the database is not open--that's weird, let's log it and bail
			case E_DB_NOT_OPEN: {
				throw SQLiteException(E_DB_NOT_OPEN);
				return -1;
			}
			// we don't have the table
			case SQLITE_OK: {
				int execRetval = database.ExecuteSQL("create table pulse_log (key INTEGER PRIMARY KEY, keys_pulsed INTEGER, timeEntered INTEGER");
				if (execRetval != 0) { 
					// aw crap, we didn't create it
					// throw an exception
					throw SQLiteException(execRetval);
				}
				/* if we get here, then goody, we created it, press on! */
				break;
			}
			case 1: {
				// We have the table, just break;
				break;
			}
			default: { 
				// oh shit. something's wrong, retval > 0 and not = 1
				// log the sqlite error and bail
				throw SQLiteException(retval);
			}
		} // okay, so we've made sure that we either a) have a table or b) have created it if we didn't have it

		// now that we know the database is open, and we have our table, let's just push that sucker in
		int execRetval = database.ExecuteSQL(Utility::FormatString("insert into pulse_log (keys_pulsed, timeEntered) values (%li, %i);", 
			Pulse.Keys(), static_cast<int>(time(NULL))));
		
		// if we get back SQLITE_OK, that means we inserted sucessfully
		if (execRetval == SQLITE_OK) {
			// format a message and log it, then return SQLITE_OK
			Utility::LogMessage(Utility::FormatString(_("Inserted pulse into database succesfully at %s"), 
				Utility::CurrentDateAndTime().c_str()), M_INFO);
			return SQLITE_OK;
		} else {
			// format an error message, and log it, then return the error
			Utility::LogMessage(Utility::FormatString(_("Could not insert pulse into database, SQLite gave error: %d (%s)"), 
				execRetval, database.GetLastError().c_str()), M_ERROR);
			return execRetval;
		}		
	} catch (SQLiteException& e) {
		// if we failed to open the database, return 0
		return E_DB_NOT_OPEN;
	}
}

// pulse if we should (ie. if it's defined in the configuration file)
bool Pulse::DoPulse(unsigned long Keys) {
	// set up our variables, and read the ones that are in the config file
	// into the variable
	bool retVal;
	Config configuration;
	configuration.readFile(strdup(DEFAULT_CONFIG_FILE));
	PulseInfo pulse(Keys, configuration.lookup("application.pulse.username"), configuration.lookup("application.pulse.password"));
	// check if we're pulsing to the web
	if (static_cast<bool>(configuration.lookup("application.pulse.toweb"))) {
		// if we are, use the pulse Utility class, and catch the return value
		// if it's 200, log that we succeded, zero the file, reset the keycount and set
		// last_pulse to now.
		int retval = Pulse::PulseToWeb(pulse);
		if (retval == HTTP_STATUS_CODE_OK) {			
			Utility::LogMessage(Utility::FormatString(_("Successfully pulsed to %s at %s, %li keys sent."), 
				static_cast<const char*>(configuration.lookup("application.pulse.server")),
				Utility::CurrentDateAndTime().c_str(), Keys), 
				M_INFO);	
			Pulse::ZeroPulseFile(); // reset the on-disk file, since we just sent off its info
			retVal = retVal & true;
		} else {
			retVal = retVal & false;
		}
	}
	// check if we're pulsing to the db
	if (static_cast<bool>(configuration.lookup("application.pulse.todb"))) {
		// if we are, repeat the exact same process as we did for http
		int retval = Pulse::PulseToDB(pulse);
		if (retval == SQLITE_OK) {
			Utility::LogMessage(Utility::FormatString(_("Successfully pulsed to database at %s, %li keys sent."),
			Utility::CurrentDateAndTime().c_str(), Keys), M_INFO);
			Pulse::ZeroPulseFile(); // reset the on-disk file, since we just sent off its info
			retVal = retVal & true;
		} else {
			retVal = retVal & false;
		}
	}
	
	return retVal;
}
